6.7 Abstrakte Klassen und Methoden  
6.7.1 Problembeschreibung  
Je weiter man nach »oben« in einer Klassenhierarchie geht, desto allgemeiner und unverbindlicher werden die Klassen. Die oberste Klasse ist meistens so allgemein, dass man sie sich gerade noch als Gerüst oder als Oberbegriff für alle abgeleiteten Klassen vorstellen kann. Denken Sie noch einmal an das Beispiel der Klassenhierarchie der Luftfahrzeuge. Ein Programm, das diese Hierarchie nutzt, wird Instanzen der Klassen Hubschrauber, Zeppelin und Starrflügler erstellen. Den aus diesen drei Klassen erstellten Objekten sind einige Verhaltensweisen und Eigenschaften gemein: Alle Objekte werden – unabhängig vom Typ – sowohl starten als auch fliegen und landen können, alle werden einen Kaufpreis haben, einen Hersteller usw.
Haben Sie dieses Kapitel von Anfang an aufmerksam gelesen, werden Sie die Schlussfolgerung ziehen, dass man die gemeinsamen Entitäten der verschiedenen Objekte sinnvollerweise in der Basisklasse Luftfahrzeug implementiert und an die Subklassen vererbt.
Vom Ansatz her ist diese Idee richtig, aber denken Sie einen Schritt weiter: Wie soll beispielsweise die Methode Starten in Luftfahrzeug implementiert werden? Ein Starrflügler wird anders starten als ein Hubschrauber und ein Hubschrauber wiederum anders als ein Zeppelin. Ein Flugzeug benötigt eine Startbahn mit einer Mindestlänge, um überhaupt abheben zu können, während für einen Hubschrauber bereits eine freie Fläche in einer Größe genügt, die gewährleistet, dass die Rotorspitzen keine Hindernisse streifen. Um einen Zeppelin zu starten, wird eine Mannschaft benötigt, die das Halteseil löst und das Luftschiff zum Starten freigibt.
Ein neues Problem wird plötzlich offensichtlich: Wie kann das Startverhalten in der Basisklasse implementiert werden, wenn es für die Objekte der abgeleiteten Typen Zeppelin, Hubschrauber und Starrflügler unterschiedlich beschrieben werden muss? Ein trivialer Lösungsansatz würde die Methode Starten in der Basisklasse definieren und den Startvorgang eines beliebigen Typs implementieren – zum Beispiel den eines Starrflüglers. Zugegeben, für die meisten real existierenden Flugobjekte würde damit die passende Implementierung geliefert. Die anderen beiden Typen würden diese Methode zwar erben, müssten aber darauf achten, sie mit Shadows zu verdecken und neu zu implementieren.
Das kann es aber doch nicht sein, was wir wirklich wollen: Eine bestimmte Subklasse erbt die Funktionalität ihrer Basisklasse und kann sie uneingeschränkt nutzen, alle anderen Subklassen stehen in der Verantwortung, die geerbte Methode durch eine individuelle Implementierung zu ersetzen. Wird das in der Zeppelin-Klasse versäumt, rast ein Zeppelin beim Starten über die 2 000 m lange Startbahn bis zur Abhebgeschwindigkeit von 250 km/h ... sicherlich sehr zum Leidwesen der Bodenmannschaft.
6.7.2 Abstrakte Definitionen  
Der Lösung haftet anscheinend der Nachteil an, dass sie in den abgeleiteten Klassen nicht auf die gleiche Weise behandelt werden kann. Sie ist damit fehlerträchtig und nicht gut. Jede abgeleitete Klasse sollte hinsichtlich der Behandlung einer Basisklassenoperation gleichwertig sein: Entweder müssen alle abgeleiteten Klassen die Starten-Methode neu implementieren oder keine. Nur so lassen sich potenzielle Fehler im Ansatz vermeiden.
Die Lösung der erkannten Problematik mag im ersten Moment verblüffen: Tatsächlich wird die Methode Starten in der Basisklasse nicht implementiert – sie bleibt einfach ohne Programmcode. In der objektorientierten Programmierung werden solche Methoden, die keinen Code enthalten, als abstrakte Methoden bezeichnet. Neben den das Verhalten eines Typs beschreibenden Methoden können auch Eigenschaften abstrakt definiert werden.
In Visual Basic werden abstrakte Methoden durch die Angabe des MustOverride-Modifizierers in der Methodensignatur gekennzeichnet, am Beispiel unserer Starten-Methode also:
| Public MustOverride Sub Starten()
|
Abstrakte Methoden enthalten grundsätzlich keinen Code. Im Texteditor der Entwicklungsumgebung ist das sehr deutlich daran zu erkennen, dass der ansonsten üblicherweise automatisch erzeugte Abschluss des Anweisungsblocks mit End Sub nicht vorgegeben wird.
Welchen Stellenwert nimmt aber eine Klasse ein, die eine Methode veröffentlicht, die keinerlei Verhalten aufweist? Die Antwort ist verblüffend, eine solche Klasse kann nicht instanziiert werden – sie rechtfertigt ihre Existenz einzig und allein dadurch, als Methodengeber für abgeleitete Klassen zu dienen. Damit wird das Prinzip der objektorientierten Programmierung, gemeinsame Verhaltensweisen auf eine höhere Ebene auszulagern, nahezu auf die Spitze getrieben.
Eine nicht instanziierbare Klasse, die mindestens eine durch MustOverride gekennzeichnete abstrakte Methode enthält, muss ihrerseits selbst abstrakt sein und wird deshalb auch als abstrakte Klasse bezeichnet. Abstrakte Klassen machen nur dann einen Sinn, wenn sie abgeleitet werden. Syntaktisch wird dieses Verhalten in Visual Basic durch die Ergänzung des Modifizierers MustInherit in der Klassensignatur beschrieben:
| Public MustInherit Class Luftfahrzeug
|
| Public MustOverride Sub Starten()
|
| ...
|
| End Class
|
Neben abstrakten Methoden darf eine abstrakte Klasse auch vollständig implementierte Methoden und Eigenschaften bereitstellen. So könnte die Klasse Luftfahrzeug beispielsweise den Hersteller des Luftfahrzeugs über eine Eigenschaftsmethode speichern und zurückgeben:
| Public MustInherit Class Luftfahrzeug
|
| Private strHersteller As String
|
| ' abstrakte Methode
|
| Public MustOverride Sub Starten()
|
| ' konkrete Methode
|
| Public Property Hersteller() As String
|
| Get
|
| Return strHersteller
|
| End Get
|
| Set
|
| strHersteller = Value
|
| End Set
|
| End Property
|
| End Class
|
Aus dem Modifizierer MustOverride einer abstrakten Methode beziehungsweise dem Klassenmodifizierer MustInherit wird ersichtlich, dass die Definition als abstrakte Methode einem Versprechen gleichkommt:
|
Alle nichtabstrakten Nachfolger einer abstrakten Klasse müssen alle abstrakten Methoden der Basisklasse überschreiben.
|
Wird in einer abgeleiteten Klasse das abstrakte Mitglied der Basisklasse nicht überschrieben, muss die Subklasse in jedem Fall ebenfalls als MustInherit gekennzeichnet werden. In Konsequenz dieser Aussagen bilden abstrakte Klassen das Gegenkonstrukt zu den Klassen, die mit NotInheritable als nicht ableitbar gekennzeichnet sind.
Im folgenden Codefragment ist die Klasse Hubschrauber definiert. In der Klassenimplementierung wird die Starten-Methode der Basisklasse überschrieben. Dazu wird die Methode mit dem Modifizierer Overrides signiert:
| Public Class Hubschrauber
|
| Inherits Luftfahrzeug
|
| Public Overrides Sub Starten()
|
| Console.WriteLine("Der Hubschrauber startet.")
|
| End Sub
|
| End Class
|
Eine Klasse, die eine abstrakt definierte Methode enthält, muss ihrerseits selbst abstrakt sein. Der Umkehrschluss ist allerdings nicht richtig, denn eine abstrakte Klasse ist nicht zwangsläufig dadurch gekennzeichnet, ein abstraktes Mitglied vorzuweisen: Eine Klasse kann auch dann abstrakt sein, wenn keine ihrer Entitäten abstrakt ist.
|